/* **************************************************************************
 * $OpenLDAP: /com/novell/sasl/client/TokenParser.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
 *
 * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
 *
 * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
 * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
 * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
 * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
 * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
 * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
 * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
 * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
 ******************************************************************************/
package com.novell.sasl.client;

import org.apache.harmony.javax.security.sasl.*;

/**
 * The TokenParser class will parse individual tokens from a list of tokens that
 * are a directive value for a DigestMD5 authentication.The tokens are separated
 * commas.
 */
class TokenParser extends Object {
	private static final int STATE_LOOKING_FOR_FIRST_TOKEN = 1;
	private static final int STATE_LOOKING_FOR_TOKEN = 2;
	private static final int STATE_SCANNING_TOKEN = 3;
	private static final int STATE_LOOKING_FOR_COMMA = 4;
	private static final int STATE_PARSING_ERROR = 5;
	private static final int STATE_DONE = 6;

	private int m_curPos;
	private int m_scanStart;
	private int m_state;
	private String m_tokens;

	TokenParser(String tokens) {
		m_tokens = tokens;
		m_curPos = 0;
		m_scanStart = 0;
		m_state = STATE_LOOKING_FOR_FIRST_TOKEN;
	}

	/**
	 * This function parses the next token from the tokens string and returns it
	 * as a string. If there are no more tokens a null reference is returned.
	 * 
	 * @return the parsed token or a null reference if there are no more tokens
	 * 
	 * @exception SASLException
	 *                if an error occurs while parsing
	 */
	String parseToken() throws SaslException {
		char currChar;
		String token = null;

		if (m_state == STATE_DONE)
			return null;

		while (m_curPos < m_tokens.length() && (token == null)) {
			currChar = m_tokens.charAt(m_curPos);
			switch (m_state) {
			case STATE_LOOKING_FOR_FIRST_TOKEN:
			case STATE_LOOKING_FOR_TOKEN:
				if (isWhiteSpace(currChar)) {
					break;
				} else if (isValidTokenChar(currChar)) {
					m_scanStart = m_curPos;
					m_state = STATE_SCANNING_TOKEN;
				} else {
					m_state = STATE_PARSING_ERROR;
					throw new SaslException(
							"Invalid token character at position " + m_curPos);
				}
				break;

			case STATE_SCANNING_TOKEN:
				if (isValidTokenChar(currChar)) {
					break;
				} else if (isWhiteSpace(currChar)) {
					token = m_tokens.substring(m_scanStart, m_curPos);
					m_state = STATE_LOOKING_FOR_COMMA;
				} else if (',' == currChar) {
					token = m_tokens.substring(m_scanStart, m_curPos);
					m_state = STATE_LOOKING_FOR_TOKEN;
				} else {
					m_state = STATE_PARSING_ERROR;
					throw new SaslException(
							"Invalid token character at position " + m_curPos);
				}
				break;

			case STATE_LOOKING_FOR_COMMA:
				if (isWhiteSpace(currChar))
					break;
				else if (currChar == ',')
					m_state = STATE_LOOKING_FOR_TOKEN;
				else {
					m_state = STATE_PARSING_ERROR;
					throw new SaslException("Expected a comma, found '"
							+ currChar + "' at postion " + m_curPos);
				}
				break;
			}
			m_curPos++;
		} /* end while loop */

		if (token == null) { /* check the ending state */
			switch (m_state) {
			case STATE_SCANNING_TOKEN:
				token = m_tokens.substring(m_scanStart);
				m_state = STATE_DONE;
				break;

			case STATE_LOOKING_FOR_FIRST_TOKEN:
			case STATE_LOOKING_FOR_COMMA:
				break;

			case STATE_LOOKING_FOR_TOKEN:
				throw new SaslException("Trialing comma");
			}
		}

		return token;
	}

/**
     * This function returns TRUE if the character is a valid token character.
     *
     *     token          = 1*<any CHAR except CTLs or separators>
     *
     *      separators     = "(" | ")" | "<" | ">" | "@"
     *                     | "," | ";" | ":" | "\" | <">
     *                     | "/" | "[" | "]" | "?" | "="
     *                     | "{" | "}" | SP | HT
     *
     *      CTL            = <any US-ASCII control character
     *                       (octets 0 - 31) and DEL (127)>
     *
     *      CHAR           = <any US-ASCII character (octets 0 - 127)>
     *
     * @param c  character to be validated
     *
     * @return True if character is valid Token character else it returns 
     * false
     */
	boolean isValidTokenChar(char c) {
		if (((c >= '\u0000') && (c <= '\u0020'))
				|| ((c >= '\u003a') && (c <= '\u0040'))
				|| ((c >= '\u005b') && (c <= '\u005d')) || ('\u002c' == c)
				|| ('\u0025' == c) || ('\u0028' == c) || ('\u0029' == c)
				|| ('\u007b' == c) || ('\u007d' == c) || ('\u007f' == c))
			return false;

		return true;
	}

	/**
	 * This function returns TRUE if the character is linear white space (LWS).
	 * LWS = [CRLF] 1*( SP | HT )
	 * 
	 * @param c
	 *            character to be validated
	 * 
	 * @return True if character is liner whitespace else it returns false
	 */
	boolean isWhiteSpace(char c) {
		if (('\t' == c) || // HORIZONTAL TABULATION.
				('\n' == c) || // LINE FEED.
				('\r' == c) || // CARRIAGE RETURN.
				('\u0020' == c))
			return true;

		return false;
	}

}
